package mygame;

import character.AbstractCharacter;
import character.action.BuildAction;
import character.action.MoveAction;
import character.action.HarvestAction;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.export.binary.BinaryImporter;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import gameobject.construct.Construct;
import gameobject.construct.Ramp;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class InGameInputHandler implements ActionListener, AnalogListener {
    protected boolean selectMultiple;
    protected int currentViewLayer = 0;

    public void init(InputManager inputManager) {
	inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
	inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
	inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
	inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
	inputManager.addMapping("Harvest", new KeyTrigger(KeyInput.KEY_X));
	inputManager.addMapping("Build", new KeyTrigger(KeyInput.KEY_B));
	inputManager.addMapping("RotateLeft", new KeyTrigger(KeyInput.KEY_LEFT));
	inputManager.addMapping("RotateRight", new KeyTrigger(KeyInput.KEY_RIGHT));
	inputManager.addMapping("LayerDown", new KeyTrigger(KeyInput.KEY_PGDN));
	inputManager.addMapping("LayerUp", new KeyTrigger(KeyInput.KEY_PGUP));
        inputManager.addMapping("Pause", new KeyTrigger(KeyInput.KEY_P));
	inputManager.addMapping("Save", new KeyTrigger(KeyInput.KEY_LBRACKET));
	inputManager.addMapping("Load", new KeyTrigger(KeyInput.KEY_RBRACKET));
	inputManager.addMapping("SelectMultiple", new KeyTrigger(KeyInput.KEY_LSHIFT));
	inputManager.addMapping("MouseSelect", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
	inputManager.addMapping("MouseAction", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
	
	inputManager.addListener(this, "Left", "Right", "Up", "Down", "Harvest", "Build", "RotateLeft", "RotateRight", "LayerDown", "LayerUp", "Pause", "Save", "Load", "SelectMultiple", "MouseSelect", "MouseAction");
    }

    @Override
    public void onAnalog(String name, float value, float tpf) {
	//TODO override cam controls
	if(name.equals("Left")) {
	    
	} else if(name.equals("Right")) {
	    
	} else if(name.equals("Up")) {
	    
	} else if(name.equals("Down")) {
	    
	}
    }

    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
	if(name.equals("MouseSelect") && isPressed) {
	    doMouseSelect();
	} else if(name.equals("MouseAction") && isPressed) {
	    doMouseAction();
	} else if(name.equals("Harvest") && isPressed) {
	    if(DataStore.player.hasSelectedCharacter()) {
		DataStore.player.toggleSelectedAction("Harvest");
		System.out.println("Current Action: "+DataStore.player.getSelectedAction());
	    }
	} else if(name.equals("Build") && isPressed) {
	    if(DataStore.player.hasSelectedCharacter()) {
		if(!DataStore.player.hasPlacer())
                    createPlacer();
                else
                    cleanAndRemovePlacer();
	    }
	} else if(name.equals("RotateLeft") && isPressed) {
	    if(DataStore.player.hasPlacer())
		DataStore.player.getPlacer().rotateLeft();
	} else if(name.equals("RotateRight") && isPressed) {
	    if(DataStore.player.hasPlacer())
		DataStore.player.getPlacer().rotateRight();
	} else if(name.equals("LayerDown") && isPressed) {
	    if(LayerSwitcher.switchToLayer(currentViewLayer-1))
		currentViewLayer--;
	} else if(name.equals("LayerUp") && isPressed) {
	    if(LayerSwitcher.switchToLayer(currentViewLayer+1))
		currentViewLayer++;
        } else if(name.equals("Pause") && isPressed) {
            StateSwitcher.loadPausedState();
	} else if(name.equals("Save")  && isPressed) {
	    try {
                deselectAll();
		BinaryExporter exporter = BinaryExporter.getInstance();
		exporter.save(DataStore.worldStateNode, new File("game.save"));
	    } catch (IOException ex) {
		Logger.getLogger(InGameInputHandler.class.getName()).log(Level.SEVERE, "Unable to save game.", ex);
	    }
	} else if(name.equals("Load") && isPressed) {
	    try {
		BinaryImporter importer = BinaryImporter.getInstance();
		importer.setAssetManager(DataStore.assetManager);
                DataStore.aiAppState.getAISpace().removeAll(DataStore.worldStateNode);
		DataStore.bulletAppState.getPhysicsSpace().removeAll(DataStore.worldStateNode);
		DataStore.rootNode.detachChild(DataStore.worldStateNode);
                DataStore.rootNode.detachChild(DataStore.voxelWireframeNode); //for debugging
		WorldStateNode newWorldStateNode = (WorldStateNode)importer.load(new File("game.save"));
		DataStore.rootNode.attachChild(newWorldStateNode);
		DataStore.bulletAppState.getPhysicsSpace().addAll(newWorldStateNode);
                DataStore.aiAppState.getAISpace().addAll(newWorldStateNode);
                
		DataStore.worldStateNode = newWorldStateNode;
                //TODO pretty sure the voxel grid is being loaded correctly... double-check after you add wireframes to voxels
	    } catch (IOException ex) {
		Logger.getLogger(InGameInputHandler.class.getName()).log(Level.SEVERE, null, ex);
	    }
	} else if(name.equals("SelectMultiple")) {
	    selectMultiple = isPressed;
	}
    }

    public void doMouseSelect() {
	deselectAll();
        //CollisionResult target = rayCastFromMouseFirstCollision(DataStore.selectableHubNode);
	CollisionResult target = rayCastFromMouseFirstCollision(DataStore.worldStateNode); //here for development purposes
        if(target != null) {
            if(target.getGeometry().hasAncestor(DataStore.worldStateNode.getHubNodeCharacter()))
		selectCharacter(target.getGeometry());
	    else if(target.getGeometry().hasAncestor(DataStore.worldStateNode.getHubNodeSelectableConstruct()))
		selectConstruct(target.getGeometry());
	    else if(target.getGeometry().hasAncestor(DataStore.worldStateNode.getHubNodeUnselectableConstruct()))
		WorldManager.removeConstructFromWorld(getParentConstruct(target.getGeometry()));
        }
    }

    public void selectCharacter(Spatial characterSpatial) {
	AbstractCharacter character = getParentCharacter(characterSpatial);
	System.out.println("Selected: "+character+"\nInventory: "+character.getInventory());
	DataStore.player.setSelectedCharacter(character);
    }

    public AbstractCharacter getParentCharacter(Spatial characterSpatial) {
	while(!(characterSpatial instanceof AbstractCharacter)) {
            if(characterSpatial.getParent() != null)
                characterSpatial = characterSpatial.getParent();
            else
                return null;
        }
	return (AbstractCharacter)characterSpatial;
    }

    public void selectConstruct(Spatial constructSpatial) {
	Construct construct = getParentConstruct(constructSpatial);
	System.out.println("Selected: "+construct+"\nHit points: "+construct.getHitPoints()+"\nInventory: "+construct.getInventory());
	DataStore.player.setSelectedConstruct(construct);
    }

    public Construct getParentConstruct(Spatial constructSpatial) {
	while(!(constructSpatial instanceof Construct)) {
            if(constructSpatial.getParent() != null)
                constructSpatial = constructSpatial.getParent();
            else
                return null;
        }
	return (Construct)constructSpatial;
    }

    public void deselectAll() {
	DataStore.player.clearSelectedCharacter();
	DataStore.player.clearSelectedConstruct();
	if(DataStore.player.hasPlacer())
	    cleanAndRemovePlacer();
    }
    
    public void doMouseAction() {
	CollisionResult target = rayCastFromMouseFirstCollision(DataStore.worldStateNode);
        if(target != null) {
            if(DataStore.player.hasSelectedCharacter()) {
                if(!selectMultiple)
                    DataStore.player.getSelectedCharacter().clearActionQueue();
                
                if(DataStore.player.hasSelectedAction()) {
                    if(DataStore.player.getSelectedAction().equals("Harvest")) {
                        doHarvest(target);
                    } else if(DataStore.player.getSelectedAction().equals("Build")) {
                        doBuild(target);
                    }
                } else {
                    DataStore.player.getSelectedCharacter().queueActionBack(new MoveAction(target.getContactPoint()));
                }
            }
        }
    }
    
    private void doHarvest(CollisionResult target) {
        Construct construct = getParentConstruct(target.getGeometry());
        if(construct != null) {
            DataStore.player.getSelectedCharacter().queueActionBack(new HarvestAction(construct));
        }
    }
    
    private void doBuild(CollisionResult target) {
        if(target.getGeometry().hasAncestor(DataStore.worldStateNode.getHubNodeUnselectableConstruct())) {
            if(placerCanBePlaced()) { //a placer is assumed to exist already here
                WorldManager.addConstructToWorld(DataStore.player.getPlacer(), DataStore.worldStateNode.getHubNodeSelectableConstruct());
                DataStore.player.getSelectedCharacter().queueActionBack(new BuildAction(DataStore.player.getPlacer()));
                cleanPlacer();
            } else {
                System.out.println("Can't place that there!");
            }
        } else if(target.getGeometry().hasAncestor(DataStore.worldStateNode.getHubNodeSelectableConstruct())) {
            Construct construct = getParentConstruct(target.getGeometry());
            if(!construct.isBuilt()) {
                //TODO resuming building doesn't work anymore
                DataStore.player.getSelectedCharacter().queueActionBack(new BuildAction(construct));
            }
        }
    }

    private Construct createPlacer() {
	Construct placer = Ramp.createInstance();
	placer.addControl(new CursorControl(DataStore.inputManager, DataStore.player.getCamera()));
	WorldManager.addConstructToWorldTemporary(placer);
	DataStore.player.setPlacer(placer);
	DataStore.player.setSelectedAction("Build");
	return placer;
    }

    private Construct cleanPlacer() {
	Construct placer = DataStore.player.getPlacer();
	placer.removeControl(CursorControl.class);
	DataStore.player.clearPlacer();
	DataStore.player.clearSelectedAction();
	return placer;
    }

    private void cleanAndRemovePlacer() {
	Construct placer = cleanPlacer();
	WorldManager.removeConstructFromWorld(placer);
    }
    
    private boolean placerCanBePlaced() {
        Construct placer = DataStore.player.getPlacer();
        for(Voxel occupiedVoxel : placer.getOccupiedVoxels()) {
            if(DataStore.worldStateNode.getVoxelGrid().isVoxelAtLocation(occupiedVoxel.getVoxelGridLocation()))
                return false;
        }
        return true;
    }
    
    public CollisionResult rayCastFromMouseFirstCollision(Node onNode) {
        CollisionResults results = new CollisionResults();
	Vector2f click2d = DataStore.inputManager.getCursorPosition();
	Vector3f click3d = DataStore.player.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
	Vector3f dir = DataStore.player.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d).normalizeLocal();
	Ray ray = new Ray(click3d, dir);
        onNode.collideWith(ray, results);
        if (results.size() > 0) {
	  CollisionResult closest = results.getClosestCollision();
          return closest;
        }
        return null;
    }
}
